<?php

/* Class to handle Dataxi sesions. This is used by dzManager.
 * This collection of functions (libary) is licensed under GPL2.
 * See LICENSE or www.gnu.org for more details.
 * @author Henri Ranki
 *
 * $Id: dzsession.php 595 2006-04-03 16:00:47Z jyry $
 */


include_once("sodebug.php");
include_once("sosqlhi.php");
include_once("sostring.php");
include_once("soset.php");
include_once("soconst.php");
include_once("solist.php");
include_once("dzviewstack.php");
include_once("dzfilter.php");
include_once("dzerror.php");


/** Structure to hold general session information
 *  This structure is pointed from the classes below dzSession too.
 *  Several classes need user and session information. They will receive
 *  a pointer to this structure.
 *  @var int $Id Session id
 *  @var string $UserID Session owner's login name
 *  @var string $UserName Session owner's full name
 *  @var int $UserLevel The user's security level
 *  @var array/string $Groups The user's groups
 *  @var string $Lang User's language
 */
class dzSessionInfo {
    var $Id;
    var $UserID;
    var $UserName;
    var $UserLevel;
    var $Groups;
    var $Lang;


    function dzSessionInfo() {
        $this->Groups = array();
    } // dzSessionInfo


} // dzSessionInfo


/** Class to handle session related data
 *  @var int $FocusGroup The group ID that has focus on the form
 *  @var int $FocusRow The row that has the focus
 *  @var int $FocusCol The column that has the focus
 *  @var string $FocusFieldName The name of the column that has the focus.
 *  @var int $State The state of the session
 *  @var bool $Suspend True if the session is suspended
 *  @var dzError $ErrorLog Heap for error that occurs during one request.
 *  @var dzView $HotView The active view. This is the topmost view in
 *       view stack.
 *  @var dzViewStack $ViewStack The storage for open views
 *  @var dzSessionInfo $BasicInfo Basic information for the session
 *  @var string $LastAccess time of last access
 *  @var any $Context Used to pass parameters from state to state in the
 *       state machine
 *  @var soList/int $ViewOrder Order of views in the Stonga list box
 *  @var array/string $ViewTitles Ids and titles for the views
 *  @var int $ViewPos Current view pos. (-1 = no view active)
 *  @var int $ViewCnt Counter used to produce unique view ids
 *
 *  @var int $LastState This is the last state before leaving to client side.
 *       This information is needed when changing the view.
 *  @var soSet $SessionFlags Session wide flags. These usually indicate
 *       permission to some action.
 */
class dzSession {

    /** @Public */
    var $FocusGroup;
    var $FocusRow;
    var $FocusCol;
    var $FocusFieldName;

    var $State;
    var $Suspend;
    var $ErrorLog;
    var $HotView;
    var $ViewStack;
    var $BasicInfo;
    var $LastAccess;
    var $Context;

    /** @private */
    var $ViewOrder;
    var $ViewTitles;
    var $ViewPos = -1;
    var $ViewCnt = 0;
    var $LastState = 0;
    var $SessionFlags;
    var $__DZ_GLOBALS;


    /** Constructor.
     *  @param string $SesId Id for the new session
     */
    function dzSession($SesId) {
        soDebug("dzSession: constructor Id=$SesId", SOD_DET);

        /* Fill in the basic session information */
        $this->BasicInfo = new dzSessionInfo();
        $this->BasicInfo->Id = $SesId;

        $this->ViewOrder = new soList();
        $this->ViewTitles = array();
        $this->State = 0;
        $this->SessionFlags = new soSet();
        $this->ErrorLog = new dzError();

        $this->ViewStack = NULL;
    } // constructor


    /** Adds a new view stack to session
     *  View stack has set of subviews and the main view. This method
     *  creates the main view too.
     *  @param string $HtmlName the source file for Dataxi
     *  @return int The id of the new view stack or -1 on error
     */
    function addViewStack($HtmlName) {
        $new_view_stack = new dzViewStack($this->ViewCnt, $this->BasicInfo);

        /* Insert new view to the top of the view stack */
        $view_data = new dzViewData(DZ_VIEW_TYPE_DATAXI, false, $HtmlName, false);
        $new_view = $new_view_stack->pushView(NULL, $view_data);

        soDebug("dzSession->addViewStack '".$newView->Id."'", SOD_DET);

        /* If the parsing of the source was succesful then proceed. Otherwise
         * show error message and the view is discarted */
        switch ($new_view->Error) {
            case  DZ_TMPL_PARSE_FAILED:
                $err_msg = new dzErrorMsg(DZER_SRC_PARSE_FAILED, "",
                                          DZERT_SOURCE, DZERL_ERROR);
                $this->ErrorLog->add($err_msg);
                $result = -1;
                break;

            case  DZ_TMPL_CACHE_WRITE_FAILED:
                $err_msg = new dzErrorMsg(DZER_SRC_CACHE_WRITE_FAILED, "",
                                        DZERT_SOURCE, DZERL_ERROR);
                $this->ErrorLog->add($err_msg);

                /* This is not fatal error. We can proceed and start
                using the view. (-> No break here!) */

            default:
                $this->storeViewStack($new_view_stack);
                $this->ViewOrder->insItem($this->ViewCnt, 0);
                $this->ViewCnt = $this->ViewCnt + 1; /* Advance the view counter */
                $result = $new_view_stack->Id;
        } // switch

        return $result;
    } //addViewStack()


    /** Deletes a view stack from the session
     *  @param int Id The id of the view stack to delete
     */
    function deleteViewStack($Id) {
        soDebug("dzManager: deleteView: Id=$Id ", SOD_DET);
        $pos = $this->ViewOrder->findItem($Id);
        if ($pos < 0) {
            soDebug("Manager, deleteView: Can't find the given view id.",
                     SOD_LO_E);
            return; // <--- EXIT
        }

        $this->ViewOrder->delItem($pos);

        /* Are we deleting the current view */
        if ($pos == $this->ViewPos) {
            /* Set the current view null */
            $this->ViewStack = NULL;
            $this->viewPos = -1;
        }
        $this->removeViewStackFile($Id);
    } // deleteViewStack


    /** Restores a view stack from disk (unserialise)
     *  @param int Id The id of the view stack to be restored
     *  @return dzViewStack The restored view stack
     */
    function restoreViewStack($Id) {
        /* Construct the filename where data is stored */
        $filename = __getViewSessionStreamPath($this->BasicInfo->Id."_".$Id).
                    GZ_POSTFIX;
        soDebug("dzSession: restoring view from '$filename'", SOD_DET);
        $inStream = new soInputStream($filename, 0, 1);
        $viewData = unserialize($inStream->Data);
        if ($viewData->Error) {
            $viewData=-1;
            soDebug("Session: No view '$filename' exists", SOD_HI);
        } // $viewData->Error

        return $viewData;
    } //restoreViewStack


    /** Serialises a view stack on the disk
     *  @param dzViewStack View The view stack to serialize and store
     *  @return bool True(1) if change ok, else false(0)
     */
    function storeViewStack($View) {
        if (!is_object($View)) return; // <-- EXIT
        $filename = __getViewSessionStreamPath($this->BasicInfo->Id.
                                               "_".$View->Id).GZ_POSTFIX;
        /* Create a stream to save data */
        $stream = new soOutputStream($filename, serialize($View), "", 1);

        return 1;
    } // storeViewStack


    /** Removes the view stack file from the disk
     *  This file was used to store the serialized view stack
     *  @param int Id The id of the view stack which file to delete
     *  @return bool True(1) if removed fine, else false(0)
     */
    function removeViewStackFile($Id) {
        $filename = __getViewSessionStreamPath($this->BasicInfo->Id."_$Id", 1).
                    GZ_POSTFIX;
        soDebug("dzSession: Delete session file '$filename'", SOD_DET);
        unlink($filename);
        return 1;
    } // removeViewStackFile


    /** Changes the current view stack to the given view stack
     *  Serialises the old view stack. If an error occurs while restoring the
     *  new view stack the old is kept as the current view stack.
     *  @param int $Id The id of the view stack to change
     *  @return bool True(1) if change ok, else false(0).
     */
    function changeToViewStack($Id) {
        soDebug("dzSession: changeToView $Id", SOD_DET);
        /* Preserve the state this view was in, before changing */
        if ($this->ViewStack != NULL) {
            /* Store the active view */
            $this->ViewStack->setTopView($this->HotView);
            /* If there is disposable views discard them  */
            $this->HotView = $this->ViewStack->disposeViews();

            /* Update the view title after disposing some sub views */
            if ($this->HotView != NULL)
                $this->updateCurrentViewTitle();
            /* If all views were disposed discard this view control. That
               means we do not serialize it. */
            $viewCnt = $this->ViewStack->getViewCount();
            if ($viewCnt) {
                soDebug("dzSession->changeToView: Storing view", SOD_DET);
                $this->storeViewStack($this->ViewStack);
            }
        }
        $res = $this->restoreViewStack($Id);
        if ($Res != -1) {
            $this->ViewStack = $res;
            /* Set the active view */
            $this->HotView = $this->ViewStack->getTopView();
            /* Find the current view from the list of open views */
            $this->ViewPos = $this->ViewOrder->findItem($Id);

            return true;
        } else
            return false;
    } //changeToViewStack


    /** Checks for a session flag.
     *  Check whether a session flag exists or not. Flag can be erased if it
     *  was ment to be used only once.
     *  @param string $Name Name of the flag
     *  @param bool $Erase True if the flag will be erased
     *  @return bool True(1) if flag exist. Otherwise false(0).
     */
    function querySesFlag($Name, $Erase = 1) {
        $result = $this->SessionFlags->itemExists($Name);
        if ($Erase) {
            $this->SessionFlags->delItem($Name);
        } // if

        return $result;
    } // querySesFlag


    /** Gets the value of a session flag.
     *  @param string $Name Name of the flag
     *  @param bool $Erase True if the flag will be erased
     *  @return string The flag value
     */
    function getSesFlagVal($Name, $Erase = 1) {
        $result = $this->SessionFlags->getItem($Name);
        if ($Erase) {
            $this->SessionFlags->delItem($Name);
        } // if
        return $result;
    } // getSesFlagVal


    /** Inserts or updates session flag.
     *  @param string $Name Name of the flag
     *  @param any $Value Value related to this flag
     */
    function setSesFlag($Name, $Value = "") {
        soDebug("dzSession: setSesFlag($Name, $Value)", SOD_DET);
        $this->SessionFlags->setItem($Name, $Value);
    } // setSesFlag


    /** Build the html code for the current view in the current state
     *  @return bool False(0) on error otherwise true(1)
     */
    function buildForm() {
        soDebug("dzSession->buildForm", SOD_DET);

        $hv = &$this->HotView;
        $tpl = &$hv->Template;
        $type = $tpl->getViewData(DZ_VI_SOURCE_TYPE);
        $view_type = $tpl->getViewData(DZ_VI_TYPE);
        if ($view_type == DZ_VIEW_TYPE_DIALOG) {
            $purkki_ptr = new dzPurkki();
        } else {
            /* Perform type conversion for the data in Tiski. The
               converted data will be stored in TULLI. Not in QUERY mode */
            $filter = new dzFilter($tpl, $hv->Laituri);
            $filter->filterOut();
        } // if $view_type

        /* Pass the information that the session is suspended further for Ahjos */
        if ($this->Suspend) {
            $tpl->ScriptFeatureSpool->setAttrib(DZSA_SUSPEND, true);
        }

        /* Set the suspend timer if defined and the session is not suspended yet */
        if (!$this->Suspend) {
            $suspend_delay = dzGetIniItem(DZIS_GENERAL, DZID_SUSPEND_DELAY);
            if (intval($suspend_delay) > 0) {
                $tpl->addInitAction(DZIA_SUSPEND_TIMER,
                                    intval($suspend_delay));
            } // if
        } // if

        /* Create the output filter according to the specified output type */
        switch ($type) {
            case DZT_TYPE_HTML:
                soDebug("dzSession: Created Html output filter ", SOD_DET);
                include_once("dzouthtml.php");

                global $_DZ_GLOBALS;
                $obj_cache = &$_DZ_GLOBALS[DZGV_OBJECTS];
                if ($obj_cache != "array") $obj_cache = array();
                if (in_array("dzasm", $obj_cache)) {
                    $out_filter = $obj_cache["dzasm"];
                    $out_filter->setup($tpl, $this->LastAccess, $hv->Laituri);
                } else {
                    $out_filter = &new dzAsm($tpl, $this->LastAccess,
                                             $hv->Laituri);
                }; // else if
                $result = !$out_filter->Error;

                $this->updateViewList();
                /* If all right get the form code from the Script Seppo */
                if ($result) {
                    $form_code = $out_filter->getFormCode($this->ErrorLog);
                };
                $obj_cache["dzasm"] = $out_filter;

                break;
            default:
                soDebug("dzSession: Unknown source type", SOD_HI_E);
                $result = false;
                $form_code = "";
        } //switch $source_type

        $tpl->RequestResult->Screen = $form_code;

        return $result;
    } // buildForm


    /** Sets default values to TISKI.
     *  Sets the user defined default values to tiski. The Row parameter
     *  has influence only when group is passed.
     *  @param int $Group The group id where to set default values. If not
     *         given all fields are set the default values.
     *  @param int $Row the row number where to set default values. If not
     *         given but the group is given it means the last row.
     *  @param bool $QueryMode When set, checks default values from 'qdefault'
     *         property instead of 'default'
     */
    function setDefaultValues($Group = -1, $Row = -1, $QueryMode = 0) {
        if ($Group == -1) {
            $groups = $this->HotView->Template->getGroups();
        } else {
            $groups = array($Group);
        } // if ($Group != -1)

        if ($QueryMode) {
            $col_prop = DZCP_QDEFAULT;
        } else {
            $col_prop = DZCP_DEFAULT;
        }; // if

        foreach ($groups as $group) {
            $cols = $this->HotView->Laituri->getGroupCols($group);
            foreach (soExplode($cols, " ") as $col) {
                $default =
                    $this->HotView->Template->getColProperty(
                        $col, DZCPT_CONTROL, $col_prop);

                $auto = 0;
                switch (soCaseConv($default, "L")) {
                    case DZDCV_TIME:
                        $default = soTime();
                        break;
                    case DZDCV_LANG:
                        $default = $this->BasicInfo->Lang;
                        break;
                    case DZDCV_APPLANG:
                        $default = dzGetIniItem(DZIS_GENERAL, DZID_LANG, "app");
                        break;
                    case DZDCV_AUTO:
                        /* This is very strange piece of code.
                         * Please sort out when possible --jq */
                        $auto = 1;
                        if ($Row == -1) $default = "";
                        break;
                    default: // any other that above
                        $default = str_replace("_", " ", $default);
                } // switch ()

                /* If the default value is defined set it. */
                if ($default != "") {
                    $values = $this->HotView->Laituri->getStoreCol(TISKI, $col);

                    /* If the group is given set only one row that was passed */
                    if ($group == -1) {
                        /* Set all Rows in the column */
                        for($c = count($values); $c >= 0; $c--) {
                            if (soIsEmptyStr($values[$c])) {
                                if ($auto) $default = $c;
                                $values[$c] = $default;
                            }; // if
                        }; // for
                    } else {
                        if ($Row == -1) {
                            $set_row = count($values) -1;
                        } else {
                            $set_row = $Row;
                        }; // else if

                        if (soIsEmptyStr($values[$set_row])) {
                            $values[$set_row] = $default;
                        }; // if
                    } // else if $group == -1
                    $this->HotView->Laituri->setStoreCol(TISKI, $col,
                                                         $values, 1);
                }
            } // foreach soExplode
        } // foreach $group
    } // setDefaultValues


    /** Creates ands updates the list of view titles.
     *  XXX Henkka, could you explain this in a slightly different manner:
     *  "This is in right order.
     *  That is last used first. Updates the title for the current view. The
     *  key and the mode might have been changed."
     */
    function updateViewList() {
        /* Construct the view list to put in stonga */
        $vcount = $this->ViewOrder->count();
        $view_list = array();

        for ($c = 0; $c < $vcount; $c++) {
            $id = $this->ViewOrder->getItem($c);
            if ($c != $this->ViewPos) {
                $view_list[] = $this->ViewTitles[$id];
            } else {
                /* Update current view title */
                $this->updateCurrentViewTitle();
            } // if $c ==
        } // for

        $this->HotView->Template->ScriptFeatureSpool->setAttrib(
                                                    DZSA_STONGA_VIEW_LIST,
                                                    $view_list);
    } // updateViewList


    /** Update the title for the current active view.
     *  The title is constructed according to the data related to the view.
     */
    function updateCurrentViewTitle() {
        /* Get the active view */
        $title = $this->ViewStack->Id."/";

        /* If view is disposable the title does not change */
        $disposable = $this->HotView->Template->getViewData(DZ_VI_DISPOSABLE);
        if ($disposable) return; // <-- EXIT!!

        /* Not disposable view: store this to hot view */
        $this->HotView->Id = $this->ViewStack->Id;

        $mode = $this->HotView->Template->getViewData(DZ_VI_MODE);
        switch ($mode) {
            case DZM_QUERY:
                $title .= dzGetSysMsg("dzstonga.php", "query_mode",
                                      $this->BasicInfo->Lang);
                break;

            case DZM_LOOKUP:
                $title .= dzGetSysMsg("dzstonga.php", "lookup_mode",
                                      $this->BasicInfo->Lang);
                break;

            case DZM_BROWSE:
                $title .= dzGetSysMsg("dzstonga.php", "browse_mode",
                                      $this->BasicInfo->Lang);
                break;

            case DZM_EDIT:
                $title .= dzGetSysMsg("dzstonga.php", "edit_mode",
                                      $this->BasicInfo->Lang);
                break;

            case DZM_NEW:
                $title .= dzGetSysMsg("dzstonga.php", "new_mode",
                                      $this->BasicInfo->Lang);
                break;
        }; // switch

        $title .= "/".$this->HotView->Template->getFormTitle();

        /* Get the key values from the main table */
        $type = $this->HotView->Template->getViewData(DZ_VI_TYPE);
        if ($type == DZ_VIEW_TYPE_DATAXI) {
            $keys = $this->HotView->Laituri->getCurrentBrowseKey();
            /* Construct the string that is shown in each item view list */
            $keys = $keys->getAsArray();
            foreach ($keys as $key => $value) {
                $key_str = soJoinStr($key_str, ",", $value[0]);
            } // foreach
            if ($key_str != "")
                $title .= "/".$key_str;
        } // if $type

        /* Update the title of the current view */
        $id = $this->ViewOrder->getItem($this->ViewPos);
        $this->ViewTitles[$id] = $title;
    } // updateCurrentViewTitle


    /** Updates the sessions access time
     */
    function updateSessionAccessTime() {
        soDebug("dzSession: Updating session access time", SOD_DET);
        /* Create request Itu */
        $itu = new dzItu();
        /* set request type */
        $itu->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_UPD_ACCESS_TIME);
        /* Set session id */
        $itu->setBlockItem(DZI_CONTROL, DZK_SESSION, $this->BasicInfo->Id);
        /* Send result to Itu */
        $result_itu = sendRpcToKone($itu);
        /* check for rpc error */
        if (isRpcError($result_itu)) {
            $err_msg = new dzErrorMsg(DZER_ACCESS_UPD_FAILED, "",
                                    DZERT_SYSTEM, DZERL_WARNING);
            $SesData->ErrorLog->add($err_msg);
        } // if (isRpcError($result_itu))
    } // updateSessionAccessTime

}; // dzSession


/********************************************************************
 *
 * Functions used to serialize, unserialize and remove the serialized
 * session file.
 *
 ********************************************************************/

/** Returns a file stream name to save/restore a session to/from.
 *  @param $SessionID string Session ID
 *  @param bool $FileNameOnly When set(1), returns only file name without
 *      file stream prefix "file://"
 *  @return string File Stream name for session.
 */
function __getViewSessionStreamPath($SessionID, $FileNameOnly = 0) {
    $result = dzGetIniPath(DZID_CACHE_FOLDER)."/"."dz".$SessionID;
    if (!$FileNameOnly) $result = "file://".$result;

    return $result;
}; // __getViewSessionStreamPath


/** Restores a sessnio from file (unserialise)
 *  @param string $SesId The ID of the session to restore
 *  @return dzSession The session data or NULL on error
 */
function dzManRestoreSession($SesId) {
    global $_DZ_GLOBALS;

    $filename = __getViewSessionStreamPath($SesId).GZ_POSTFIX;

    $instream = new soInputStream($filename, 0, 1);
    if (!$instream->Error) {
        $SesData = unserialize($instream->Data);
        $_DZ_GLOBALS = $SesData->_DZ_GLOBALS;
        unset($SesData->_DZ_GLOBALS);
    } else {
        soDebug("dzManRestoreSession: Session file ".
                "'$file_name' not found.", SOD_HI);
        /* No serialized session with this id found*/
        $SesData = NULL;
    }; // if ()

    return $SesData;
} // dzManRestoreSession


/** Stores a session to disk (serialise)
 *  @SessionData dzSession $SessionData Session object to be stored.
 */
function dzManStoreSession($SessionData) {
    global $_DZ_GLOBALS;
    $SessionData->_DZ_GLOBALS = $_DZ_GLOBALS;

    /* Create a stream to save data */
    $filename = __getViewSessionStreamPath($SessionData->BasicInfo->Id).
                GZ_POSTFIX;
    $Stream = new soOutputStream($filename, serialize($SessionData), "", 1);
    soDebug("dzManStoreSession: Session saved to '$filename'", SOD_DET);

} // dzManStoreSession


/** Deletes the session file from the disk
 *  @param string $SesId Session ID
 */
function dzManRemoveSessionFile($SesId) {

    /* Construct the filename where data is stored */
    $file_mask = "dz".$SesId;
    $tmp_dir_path = dzGetIniPath(DZID_CACHE_FOLDER);
    $path_ok = file_exists($tmp_dir_path);
    if (!$path_ok) {
        dzSysError("Tmp cache folder '".$tmp_dir_path."' missing ",
                   DZSE_INSTALL, true);
#        EXIT;
    };
    $tmp_dir =  dir($tmp_dir_path);
    while (($file_name = $tmp_dir->read()) != false) {
        /* If filemask is part of the file name delete it. It must start
           the file name*/
        if (soStrPos($file_name, $file_mask) == 0) {
            soDebug("dzManager: dzManRemoveSessionFile: $file_name", SOD_DET);
            unlink($tmp_dir_path."/".$file_name);
        }// if
    }; // while
} // dzManRemoveSessionFile

?>
